// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <cmath>
#include <cstring>
#include <iostream>
#include <limits>
#include "arm_dnn_library/core/macros.h"

namespace armdnnlibrary {

// Refer from
// https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/phi/common/float16.h.
// Assume that most of the fp16 operator kernels are accelerated by NEON, so we
// only keep the naive implementation of the arithmetic operators.
// Use ARM_DNN_LIBRARY_ALIGNED(2) to ensure that each float16 will be allocated
// and aligned at least on a 2-byte boundary, which leads to efficient
// memory access of float16 struct and also makes float16 compatible
// with CUDA half, ARM float16_t data types.
struct ARM_DNN_LIBRARY_ALIGN(2) float16 {
 public:
  uint16_t x;

  // The following defaulted special class member functions
  // are added to make float16 pass the std::is_trivial test
  float16() = default;
  float16(const float16& o) = default;
  float16& operator=(const float16& o) = default;
  float16(float16&& o) = default;
  float16& operator=(float16&& o) = default;
  ~float16() = default;

  // Constructors
  inline explicit float16(float val) {
#if defined(__F16C__) && \
    (ARM_DNN_LIBRARY_ARCH_X86 || ARM_DNN_LIBRARY_ARCH_X86_64)
    x = _cvtss_sh(val, 0);

#else
    // Conversion routine adapted from
    // http://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion
    Bits v, s;
    v.f = val;
    uint32_t sign = v.si & sigN;
    v.si ^= sign;
    sign >>= shiftSign;  // logical shift
    s.si = mulN;
    s.si = s.f * v.f;  // correct subnormals
    v.si ^= (s.si ^ v.si) & -(minN > v.si);
    v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
    v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
    v.ui >>= shift;  // logical shift
    v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
    v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
    x = v.ui | sign;

#endif
  }

  inline explicit float16(bool b) : x(b ? 0x3c00 : 0) {}

  template <class T>
  inline explicit float16(const T& val)
      : x(float16(static_cast<float>(val)).x) {}

  // Assignment operators
  inline float16& operator=(bool b) {
    x = b ? 0x3c00 : 0;
    return *this;
  }

  inline float16& operator=(int8_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(uint8_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(int16_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(uint16_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(int32_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(uint32_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(int64_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(uint64_t val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(float val) {
    x = float16(val).x;
    return *this;
  }

  inline float16& operator=(double val) {
    x = float16(val).x;
    return *this;
  }

  // Conversion opertors
  inline operator float() const {
#if defined(__F16C__) && \
    (ARM_DNN_LIBRARY_ARCH_X86 || ARM_DNN_LIBRARY_ARCH_X86_64)
    return _cvtsh_ss(this->x);

#else
    // Conversion routine adapted from
    // http://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion
    Bits v;
    v.ui = this->x;
    int32_t sign = v.si & sigC;
    v.si ^= sign;
    sign <<= shiftSign;
    v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
    v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
    Bits s;
    s.si = mulC;
    s.f *= v.si;
    int32_t mask = -(norC > v.si);
    v.si <<= shift;
    v.si ^= (s.si ^ v.si) & mask;
    v.si |= sign;
    return v.f;

#endif
  }

  inline explicit operator bool() const { return (x & 0x7fff) != 0; }

  inline explicit operator int8_t() const {
    return static_cast<int8_t>(static_cast<float>(*this));
  }

  inline explicit operator uint8_t() const {
    return static_cast<uint8_t>(static_cast<float>(*this));
  }

  inline explicit operator int16_t() const {
    return static_cast<int16_t>(static_cast<float>(*this));
  }

  inline explicit operator uint16_t() const {
    return static_cast<uint16_t>(static_cast<float>(*this));
  }

  inline explicit operator int32_t() const {
    return static_cast<int32_t>(static_cast<float>(*this));
  }

  inline explicit operator uint32_t() const {
    return static_cast<uint32_t>(static_cast<float>(*this));
  }

  inline explicit operator int64_t() const {
    return static_cast<int64_t>(static_cast<float>(*this));
  }

  inline explicit operator uint64_t() const {
    return static_cast<uint64_t>(static_cast<float>(*this));
  }

  inline operator double() const {
    return static_cast<double>(static_cast<float>(*this));
  }

 private:
  union Bits {
    float f;
    int32_t si;
    uint32_t ui;
  };

  static const int shift = 13;
  static const int shiftSign = 16;

  static const int32_t infN = 0x7F800000;
  static const int32_t maxN = 0x477FE000;  // max flt16 as flt32
  static const int32_t minN = 0x38800000;  // min flt16 normal as flt32
  static const int32_t sigN = 0x80000000;  // sign bit

  static constexpr int32_t infC = infN >> shift;
  static constexpr int32_t nanN = (infC + 1)
                                  << shift;  // minimum flt16 nan as float32
  static constexpr int32_t maxC = maxN >> shift;
  static constexpr int32_t minC = minN >> shift;
  static constexpr int32_t sigC = sigN >> shiftSign;

  static const int32_t mulN = 0x52000000;  // (1 << 23) / minN
  static const int32_t mulC = 0x33800000;  // minN / (1 << (23 - shift))
  static const int32_t subC = 0x003FF;     // max flt32 subnormal downshifted
  static const int32_t norC = 0x00400;     // min flt32 normal downshifted

  static constexpr int32_t maxD = infC - maxC - 1;
  static constexpr int32_t minD = minC - subC - 1;
};

// Arithmetic operators for float16, software emulated on other CPU
inline float16 operator+(const float16& a, const float16& b) {
  return float16(static_cast<float>(a) + static_cast<float>(b));
}

inline float16 operator-(const float16& a, const float16& b) {
  return float16(static_cast<float>(a) - static_cast<float>(b));
}

inline float16 operator*(const float16& a, const float16& b) {
  return float16(static_cast<float>(a) * static_cast<float>(b));
}

inline float16 operator/(const float16& a, const float16& b) {
  return float16(static_cast<float>(a) / static_cast<float>(b));
}

inline float16 operator-(const float16& a) {
  float16 res;
  res.x = a.x ^ 0x8000;
  return res;
}

inline float16& operator+=(float16& a, const float16& b) {  // NOLINT
  a = float16(static_cast<float>(a) + static_cast<float>(b));
  return a;
}

inline float16& operator-=(float16& a, const float16& b) {  // NOLINT
  a = float16(static_cast<float>(a) - static_cast<float>(b));
  return a;
}

inline float16& operator*=(float16& a, const float16& b) {  // NOLINT
  a = float16(static_cast<float>(a) * static_cast<float>(b));
  return a;
}

inline float16& operator/=(float16& a, const float16& b) {  // NOLINT
  a = float16(static_cast<float>(a) / static_cast<float>(b));
  return a;
}

// Comparison operators
inline bool operator==(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) == static_cast<float>(b);
}

inline bool operator!=(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) != static_cast<float>(b);
}

inline bool operator<(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) < static_cast<float>(b);
}

inline bool operator<=(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) <= static_cast<float>(b);
}

inline bool operator>(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) > static_cast<float>(b);
}

inline bool operator>=(const float16& a, const float16& b) {  // NOLINT
  return static_cast<float>(a) >= static_cast<float>(b);
}

inline float16 raw_uint16_to_float16(uint16_t a) {
  float16 res;
  res.x = a;
  return res;
}

inline bool(isnan)(const float16& a) { return (a.x & 0x7fff) > 0x7c00; }

inline bool(isinf)(const float16& a) { return (a.x & 0x7fff) == 0x7c00; }

inline bool(isfinite)(const float16& a) {
  return !((isnan)(a)) && !((isinf)(a));
}

inline float16(abs)(const float16& a) {
  return float16(std::abs(static_cast<float>(a)));
}

inline std::ostream& operator<<(std::ostream& os, const float16& a) {
  os << static_cast<float>(a);
  return os;
}

}  // namespace armdnnlibrary

namespace std {

// Override the std::is_pod::value for float16
// The reason is that different compilers implemented std::is_pod based on
// different C++ standards. float16 class is a plain old data in C++11 given
// that it is both trivial and standard_layout.
// However, std::is_pod in nvcc 8.0 host c++ compiler follows C++0x and is
// more restricted in that you cannot provide any customized
// constructor in float16. Hence, we override is_pod here following C++11
// so that .cu files can be successfully compiled by nvcc.
template <>
struct is_pod<armdnnlibrary::float16> {
  static const bool value = is_trivial<armdnnlibrary::float16>::value &&
                            is_standard_layout<armdnnlibrary::float16>::value;
};

template <>
struct is_floating_point<armdnnlibrary::float16>
    : std::integral_constant<
          bool,
          std::is_same<
              armdnnlibrary::float16,
              typename std::remove_cv<armdnnlibrary::float16>::type>::value> {};
template <>
struct is_signed<armdnnlibrary::float16> {
  static const bool value = true;
};

template <>
struct is_unsigned<armdnnlibrary::float16> {
  static const bool value = false;
};

inline bool isnan(const armdnnlibrary::float16& a) {
  return armdnnlibrary::isnan(a);
}

inline bool isinf(const armdnnlibrary::float16& a) {
  return armdnnlibrary::isinf(a);
}

inline bool isfinite(const armdnnlibrary::float16& a) {
  return armdnnlibrary::isfinite(a);
}

template <>
struct numeric_limits<armdnnlibrary::float16> {
  static const bool is_specialized = true;
  static const bool is_signed = true;
  static const bool is_integer = false;
  static const bool is_exact = false;
  static const bool has_infinity = true;
  static const bool has_quiet_NaN = true;
  static const bool has_signaling_NaN = true;
  static const float_denorm_style has_denorm = denorm_present;
  static const bool has_denorm_loss = false;
  static const std::float_round_style round_style = std::round_to_nearest;
  static const bool is_iec559 = false;
  static const bool is_bounded = false;
  static const bool is_modulo = false;
  static const int digits = 11;
  static const int digits10 = 3;
  static const int max_digits10 = 5;
  static const int radix = 2;
  static const int min_exponent = -13;
  static const int min_exponent10 = -4;
  static const int max_exponent = 16;
  static const int max_exponent10 = 4;
  static const bool traps = true;
  static const bool tinyness_before = false;

  static armdnnlibrary::float16(min)() {
    return armdnnlibrary::raw_uint16_to_float16(0x400);
  }
  static armdnnlibrary::float16 lowest() {
    return armdnnlibrary::raw_uint16_to_float16(0xfbff);
  }
  static armdnnlibrary::float16(max)() {
    return armdnnlibrary::raw_uint16_to_float16(0x7bff);
  }
  static armdnnlibrary::float16 epsilon() {
    return armdnnlibrary::raw_uint16_to_float16(0x1400);
  }
  static armdnnlibrary::float16 round_error() {
    return armdnnlibrary::float16(0.5);
  }
  static armdnnlibrary::float16 infinity() {
    return armdnnlibrary::raw_uint16_to_float16(0x7c00);
  }
  static armdnnlibrary::float16 quiet_NaN() {
    return armdnnlibrary::raw_uint16_to_float16(0x7e00);
  }
  static armdnnlibrary::float16 signaling_NaN() {
    return armdnnlibrary::raw_uint16_to_float16(0x7e00);
  }
  static armdnnlibrary::float16 denorm_min() {
    return armdnnlibrary::raw_uint16_to_float16(0x1);
  }
};

inline armdnnlibrary::float16 abs(const armdnnlibrary::float16& a) {
  return armdnnlibrary::abs(a);
}

}  // namespace std
